#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "LogMessage.hpp"
#include "InetAddr.hpp"

enum Error
{
    CREATESOCKETERROR = 1,
    BINDERROR,
    LISTENERROR,
};

class Socket
{
public:
    virtual ~Socket() {}
    virtual void CreateSocket() = 0;
    virtual void BindSocket(uint16_t port) = 0;
    virtual void ListenSocket() = 0;
    virtual Socket *AcceptConnect(InetAddr& addr) = 0;
    virtual bool ConnectServer(InetAddr& addr) = 0;
    virtual int Recvfrom(std::string& buffer, InetAddr* addr) = 0;
    virtual int Sendto(std::string buffer, InetAddr& addr) = 0;
    virtual int GetSockfd() = 0;
    virtual void SetSockfd(int _sockfd) = 0;
    virtual void CloseSockfd() = 0;

public:
    void CreateBindListenSocket(uint16_t port)
    {
        CreateSocket();
        BindSocket(port);
        ListenSocket();
    }

    bool CreateSocketAndConnect(InetAddr& addr)
    {
        CreateSocket();
        return ConnectServer(addr);
    }
};

const int defaultbacklog = 10;
const int defaultSockfd = -1;

class TcpSocket : public Socket
{
public:
    TcpSocket(int sockfd = defaultSockfd)
        : _sockfd(sockfd)
    {
    }

    ~TcpSocket()
    {
    }

    void CreateSocket() override
    {
        if (_sockfd > 0)
            return;

        _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            lg.LogMessage(Error, "Create listensocket error");
            exit(CREATESOCKETERROR);
        }
        int opt = 0;
        setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

        lg.LogMessage(Normal, "Create listensocket success: %d", _sockfd);
    }

    void BindSocket(uint16_t port) override
    {
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = INADDR_ANY;

        if (::bind(_sockfd, (sockaddr *)&addr, sizeof(addr)) < 0)
        {
            lg.LogMessage(Error, "Bind error");
            exit(BINDERROR);
        }

        lg.LogMessage(Normal, "Bind success");
    }

    void ListenSocket() override
    {
        if (::listen(_sockfd, defaultbacklog) < 0)
        {
            lg.LogMessage(Error, "Listen error");
            exit(LISTENERROR);
        }
        lg.LogMessage(Normal, "Listen success");
    }

    Socket *AcceptConnect(InetAddr& addr) override
    {
        sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int sockfd = ::accept(_sockfd, (sockaddr*)&peer, &len);
        if (sockfd < 0)
            return nullptr;

        addr.Init(peer);
        TcpSocket* newsock = new TcpSocket(sockfd);

        return newsock;
    }

    bool ConnectServer(InetAddr& addr) override
    {
        sockaddr_in peer = addr.GetAddr();
        int n = ::connect(_sockfd, (sockaddr*)&peer, sizeof(peer));
        if (n < 0)
            return false;
        else 
            return true;
    }

    int Recvfrom(std::string& buffer, InetAddr* addr) override
    {
        sockaddr_in peer;
        socklen_t peerlen = sizeof(peer);
        char temp[4096];
        ssize_t n = recvfrom(_sockfd, temp, sizeof(temp) - 1, 0, (sockaddr*)&peer, &peerlen);
        if (n > 0)
        {
            temp[n] = 0;
            buffer += temp;
            addr->Init(peer);
        }
        return n;
    }

    int Sendto(std::string buffer, InetAddr& addr) override
    {
        sockaddr_in peer = addr.GetAddr();
        ssize_t n = sendto(_sockfd, buffer.c_str(), buffer.size(), 0, (sockaddr*)&peer, sizeof(peer));
        return n;
    }
    
    int GetSockfd() override
    {
        return _sockfd;
    }

    void SetSockfd(int sockfd) override
    {
        _sockfd = sockfd;
    }

    void CloseSockfd() override
    {
        close(_sockfd);
    }

private:
    int _sockfd;
};